/*
myPySQL versao 1.0 - 2011-07-14

by Gustavo Vargas

if you have any doubt or sugestion please open an issue at:
http://code.google.com/p/mypysql/

Any bug you fix, or improvement you did, please consider
send me your new version so I can update my code version too.
*/

#include <Python.h>
#include "include\mysql.h"
#include <windows.h>

#define MAXSLOTS 5
#define true 1
#define false 0 

//////////////////////////////////////////////////////////////////////////
//DLL Pointers

typedef MYSQL* (STDCALL *PTRmysql_init)(MYSQL *mysql);
typedef unsigned int (STDCALL *PTRmysql_errno)(MYSQL *mysql);
typedef MYSQL*	(STDCALL *PTRmysql_real_connect)(MYSQL *mysql, const char *host,
				const char *user, const char *passwd, const char *db,
				unsigned int port, const char *unix_socket, unsigned long clientflag);
typedef void (STDCALL *PTRmysql_close)(MYSQL *sock);
typedef int	(STDCALL *PTRmysql_query)(MYSQL *mysql, const char *q);
typedef MYSQL_RES* (STDCALL *PTRmysql_store_result)(MYSQL *mysql);
typedef my_ulonglong (STDCALL *PTRmysql_num_rows)(MYSQL_RES *res);
typedef unsigned int (STDCALL *PTRmysql_num_fields)(MYSQL_RES *res);
typedef MYSQL_FIELD* (STDCALL *PTRmysql_fetch_fields)(MYSQL_RES *res);
typedef unsigned int (STDCALL *PTRmysql_field_count)(MYSQL *mysql);
typedef my_ulonglong (STDCALL *PTRmysql_affected_rows)(MYSQL *mysql);
typedef MYSQL_ROW (STDCALL *PTRmysql_fetch_row)(MYSQL_RES *result);
typedef void (STDCALL *PTRmysql_free_result)(MYSQL_RES *result);
typedef const char* (STDCALL *PTRmysql_error)(MYSQL *mysql);

//MPS stands for mypysql, I can't use the same name due to the redeclarations erros 
PTRmysql_init 			mps_init;
PTRmysql_errno 			mps_errno;
PTRmysql_real_connect 	mps_real_connect;
PTRmysql_close 			mps_close;
PTRmysql_query 			mps_query;
PTRmysql_store_result 	mps_store_result;
PTRmysql_num_rows 		mps_num_rows;
PTRmysql_num_fields 	mps_num_fields;
PTRmysql_fetch_fields 	mps_fetch_fields;
PTRmysql_field_count 	mps_field_count;
PTRmysql_affected_rows 	mps_affected_rows;
PTRmysql_fetch_row 		mps_fetch_row;
PTRmysql_free_result 	mps_free_result;
PTRmysql_error 			mps_error;

//////////////////////////////////////////////////////////////////////////
// Globals

HINSTANCE dll;

typedef struct
{
    MYSQL_RES     *mysql_res;
    my_ulonglong  rows;
    my_ulonglong  cols;
    MYSQL_ROW     mysql_row;
    MYSQL_FIELD   *mysql_field;
	char          eor;
	long          lastInsert;
} Slots;

MYSQL mysql_h;
my_ulonglong lastAffectedRows;
Slots rSlot[MAXSLOTS];

char dbHost[50];
char dbDB[20];
char dbUser[20];
char dbPass[20];
unsigned int dbPort;

unsigned int options;

char isOpen;

//////////////////////////////////////////////////////////////////////////
// Internal use functions

int _x_open(char *db, char *host, char *usr, char *pw, int port)
{
	if(!isOpen)
	{
		mps_real_connect(&mysql_h, host, usr, pw, db, port, NULL, 0);
	
		if(mps_errno(&mysql_h))
			return 1;
	}

	isOpen= true;
	
	return 0;
}

int _x_close()
{
	if(isOpen)
	{
		isOpen= false;
		
		mps_close(&mysql_h);
		
		if(mps_errno(&mysql_h))
			return 1;
	}
	
	return 0;
}

void _x_freeSlot(int slot)
{
	if(rSlot[slot].mysql_res)
	{
		mps_free_result(rSlot[slot].mysql_res);
		rSlot[slot].mysql_res= NULL;
	}
	
	rSlot[slot].rows= rSlot[slot].cols= 0;
}

int _x_next(int slot)
{
	if(!rSlot[slot].mysql_res)
		return 1;
		
	rSlot[slot].mysql_row= mps_fetch_row(rSlot[slot].mysql_res);
	
	rSlot[slot].eor= false;
	if(!rSlot[slot].mysql_row)
	{
		_x_freeSlot(slot);
		rSlot[slot].eor= true;
		return 2;
	}
	
	return 0;
}

int _x_query(int slot, char *stm)
{
    char exec;
    char local= false;
	int r;

    if(!isOpen)
    {
        if(options&1) //auto-connection
        {
            local= true;
            r= _x_open(dbDB, dbHost, dbUser, dbPass, dbPort);

            if(r)
                return r+100;
        }
        else
            return 1;
    }

    if(slot != -1)
		_x_freeSlot(slot);

    if(mps_query(&mysql_h, stm))
    {
        if(local)
			return _x_close()+200;

        return 2;
    }
    else
    {
        if(slot != -1)
        {
            rSlot[slot].mysql_res= mps_store_result(&mysql_h);

            if(rSlot[slot].mysql_res)
            {
                exec= false;

                rSlot[slot].cols= mps_num_fields(rSlot[slot].mysql_res);
                lastAffectedRows= rSlot[slot].rows= mps_num_rows(rSlot[slot].mysql_res);
                rSlot[slot].mysql_field= mps_fetch_fields(rSlot[slot].mysql_res);

                if(local)
					_x_close();

				return _x_next(slot);
            }
            else
                exec= true;
        }

        if(slot == -1 || exec)
        {
            if(mps_field_count(&mysql_h) == 0)
            {
                //nao era um select|show|help....
                lastAffectedRows= mps_affected_rows(&mysql_h);
            }
            else
            {
                //era sim um select mas nao veio dados

                if(local)
                    return _x_close()+300;

                return 3;
            }
        }
    }

    if(local)
        return _x_close();

    return 0;
}

PyObject* _x_getField(int slot, int idx)
{
	if(!rSlot[slot].rows)
		return Py_BuildValue("s", "");

	switch(rSlot[slot].mysql_field[idx].type)
	{
		case MYSQL_TYPE_TINY: //inteiro 1 bytes
		case MYSQL_TYPE_SHORT: //inteiro 2 bytes
		case MYSQL_TYPE_LONG: //inteiro 4 bytes
		case MYSQL_TYPE_LONGLONG: //inteiro 8 bytes
		case MYSQL_TYPE_INT24: //inteiro 3 bytes
		case MYSQL_TYPE_YEAR: //deveria mesmo estar aqui???????
			return Py_BuildValue("s", rSlot[slot].mysql_row[idx]);

		case MYSQL_TYPE_FLOAT: //ponto flutuantes 32 bits
		case MYSQL_TYPE_DOUBLE: //ponto flutuante 64 bits
			return Py_BuildValue("s", rSlot[slot].mysql_row[idx]);

		case MYSQL_TYPE_TIMESTAMP:
		case MYSQL_TYPE_DATE:
		case MYSQL_TYPE_TIME:
		case MYSQL_TYPE_DATETIME:
			return Py_BuildValue("s", rSlot[slot].mysql_row[idx]);

		//textos
		case MYSQL_TYPE_VARCHAR:
		case MYSQL_TYPE_VAR_STRING:
		case MYSQL_TYPE_STRING:
			return Py_BuildValue("s", rSlot[slot].mysql_row[idx]);

		case MYSQL_TYPE_TINY_BLOB:
		case MYSQL_TYPE_MEDIUM_BLOB:
		case MYSQL_TYPE_LONG_BLOB:
		case MYSQL_TYPE_BLOB:
			return Py_BuildValue("s", "<BLOB>");

		//preguica!!!! fica pra quando precisar!! :D
		case MYSQL_TYPE_NEWDATE:
		case MYSQL_TYPE_DECIMAL:  //ponto fixo
		case MYSQL_TYPE_NEWDECIMAL: //ponto fixo
		case MYSQL_TYPE_BIT:
		case MYSQL_TYPE_ENUM:
		case MYSQL_TYPE_SET:
		case MYSQL_TYPE_GEOMETRY:
			//nao deve ter break aqui!!!!

		case MYSQL_TYPE_NULL:
		default:
			break;
	}
	
	return NULL;
}

//////////////////////////////////////////////////////////////////////////
// Python exported functions

static char mypysql_doc[] = 
"mypysql is a wrapper to let you call functions from the libmysql.dll from inside Python.\n\
This wrapper exports the following functions:\n\
setOptions(ops)\n\
setAccess(db, host, user, pass, port)\n\
open(db, host, user, pass, port)\n\
close()\n\
execute(stm)\n\
query(slot, stm)\n\
rows(slot)\n\
cols(slot)\n\
next(slot)\n\
field(slot, col)\n\
getColName(slot, idx)\n\
getColType(slot, idx)\n\
affectedRows()\n\
eor(slot)\n\
getLastInsertId(slot)\n\
";

static char mypysql_setOptions_doc[]= 
"setOption(ops)\n\
Set some internal options.\n\
Actualy only one options exists:\n\
bit 0= turns on/off the auto-connect feature.\n\
It returns the current option. Use an invalid value like -1 \
the check what is the current value";

static PyObject* mypysql_setOptions(PyObject *self, PyObject *args)
{
	long a;
	
	if(!PyArg_ParseTuple(args, "l:setOptions", &a)) 
		return NULL;

	if(a > 0)
		options= a;

	return PyInt_FromLong(options);
}

static char mypysql_setAccess_doc[]= 
"setAccess(db, host, user, pass, port)\n\
Preset the connection data so you can use the auto-connection feature \
or use the open function iwthout parameters.";

static PyObject* mypysql_setAccess(PyObject *self, PyObject *args)
{
	long port;
	char *host, *db, *usr, *pw;
	
	if(!PyArg_ParseTuple(args, "ssssl:setAccess", &db, &host, &usr, &pw, &port)) 
		return NULL;

	if(!host[0] || !db[0] || !usr[0] || !pw[0])
	{
		PyErr_SetString(PyExc_RuntimeError, "You should provide all parameters.");
		return NULL;
	}
		
	strcpy(dbHost, host);
	strcpy(dbDB, db);
	strcpy(dbUser, usr);
	strcpy(dbPass, pw);
	dbPort= port;

	return Py_BuildValue("ssssl", dbDB, dbHost, dbUser, dbPass, dbPort);
}

static char mypysql_open_doc[]= 
"connect(db, host, user, password, port)\n\
Opens a connection to the mysql server.\n\
If you don't supply any parameter it will try to use the ones \
supplied to setAccess, and will raise an exception \
if they are useless.";

static PyObject* mypysql_open(PyObject *self, PyObject *args)
{
	long port;
	char *host, *db, *usr, *pw;
	
	db= dbDB;
	host= dbHost;
	usr= dbUser;
	pw= dbPass;
	port= dbPort;
	
	if(!PyArg_ParseTuple(args, "|ssssl:open", &db, &host, &usr, &pw, &port)) 
		return PyInt_FromLong(1);
	
	return PyInt_FromLong(_x_open(db, host, usr, pw, port));
}

static char mypysql_close_doc[]= 
"close()\n\
I'll give you a gift if you guess what it does.";

static PyObject* mypysql_close(PyObject *self, PyObject *args)
{
	return PyInt_FromLong(_x_close());
}

static char mypysql_execute_doc[]= 
"execute(stm)\n\
This will execute this statment over the current connection.";

static PyObject* mypysql_execute(PyObject *self, PyObject *args)
{
	char *stm;

	if(!PyArg_ParseTuple(args, "s:execute", &stm)) 
		return NULL;
		
	return PyInt_FromLong(_x_query(-1, stm));
}

static char mypysql_query_doc[]= 
"query(slot, stm)\n\
This will request data from the server and will put this data into \
slot number 'slot'.";

static PyObject* mypysql_query(PyObject *self, PyObject *args)
{
	char *stm;
	int slot;

	if(!PyArg_ParseTuple(args, "ls:query", &slot, &stm)) 
		return NULL;

	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}
		
	return PyInt_FromLong(_x_query(slot, stm));
}

static char mypysql_rows_doc[]= 
"rows(slot)\n\
Tells you how many rows there is in slot 'slot'.";

static PyObject* mypysql_rows(PyObject *self, PyObject *args)
{
	int slot;
	
	if(!PyArg_ParseTuple(args, "l:rows", &slot)) 
		return NULL;

	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}

	return PyLong_FromLong((long)rSlot[slot].rows);
}

static char mypysql_cols_doc[]= 
"colw()\n\
Tells you how many columns there is in the data of slot 'slot'.";

static PyObject* mypysql_cols(PyObject *self, PyObject *args)
{
	int slot;
	
	if(!PyArg_ParseTuple(args, "l:cols", &slot)) 
		return NULL;

	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}

	return PyLong_FromLong((long)rSlot[slot].cols);
}

static char mypysql_next_doc[]= 
"next(slot)\n\
Move to next row into slot 'slot'.\n\
If the is no more rows return false else return true.";

static PyObject* mypysql_next(PyObject *self, PyObject *args)
{
	int slot;
	
	if(!PyArg_ParseTuple(args, "l:next", &slot)) 
		return NULL;

	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}

	return PyInt_FromLong(_x_next(slot));
}

static char mypysql_field_doc[]= 
"field(slot, col)\n\
Returns the data from 'slot' for the current rows and column 'col'\n\
'col' can be a string with the columns name ou a integer with the \
index of desired column";

static PyObject* mypysql_field(PyObject *self, PyObject *args)
{
	int slot;
	int idx;
	char *col;
	int i;
	PyObject *o_slot;
	PyObject *o_sec;

    if(!PyArg_UnpackTuple(args, "field", 2, 2, &o_slot, &o_sec))
		return NULL;
	
	slot= (int)PyLong_AsLong(o_slot);
	
	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}
		
	if(!rSlot[slot].rows)
	{
		PyErr_SetString(PyExc_RuntimeError, "There is no data to fetch.");
		return NULL;
	}

/*	if(PyUnicode_Check(o_sec))
		return Py_BuildValue("s", "unicode");	
	if(PyLong_Check(o_sec))
		return Py_BuildValue("s", "long");	
	if(PyBytes_Check(o_sec))
		return Py_BuildValue("s", "byte");	
	if(PyByteArray_Check(o_sec))
		return Py_BuildValue("s", "byte[]");	
	if(PyTuple_Check(o_sec))
		return Py_BuildValue("s", "tuple");	
	if(PyList_Check(o_sec))
		return Py_BuildValue("s", "list");	

	return Py_BuildValue("s", "nenhum");	*/

	if(PyBytes_Check(o_sec))
	{
		col= PyBytes_AsString(o_sec);
		
		for(i= 0, idx= -1; i < rSlot[slot].cols; i++)
			if(!strcmp(rSlot[slot].mysql_field[i].name, col))
			{
				idx= i;
				break;
			}
			
		if(idx == -1)
		{
			PyErr_SetString(PyExc_RuntimeError, "Column name not found.");
			return NULL;
		}
	}
	else
		idx= (int)PyLong_AsLong(o_sec);

	if(idx < 0 || idx > rSlot[slot].cols)
	{
		PyErr_SetString(PyExc_RuntimeError, "The column index is out of range.");
		return NULL;
	}
		
	return _x_getField(slot, idx);
}

static char mypysql_getColName_doc[]= 
"getColName(slot, idx)\n\
Returns the name of the column index 'idx' from 'slot'.";

static PyObject* mypysql_getColName(PyObject *self, PyObject *args)
{
	int slot;
	int idx;
	
	if(!PyArg_ParseTuple(args, "ll:getColName", &slot, &idx)) 
		return NULL;

	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}

	if(!rSlot[slot].rows)
	{
		PyErr_SetString(PyExc_RuntimeError, "There is no data to fetch.");
		return NULL;
	}

	if(idx < 0 || idx > rSlot[slot].cols)
	{
		PyErr_SetString(PyExc_RuntimeError, "The column index is out of range.");
		return NULL;
	}
		
	return Py_BuildValue("s", rSlot[slot].mysql_field[idx].name);
}

static char mypysql_getColType_doc[]= 
"getColTye(slot, idx)\n\
Return the data type of the column with index 'idx' from 'slot'.";

static PyObject* mypysql_getColType(PyObject *self, PyObject *args)
{
	int slot;
	int idx;
	
	if(!PyArg_ParseTuple(args, "ll:getColType", &slot, &idx)) 
		return NULL;

	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}

	if(!rSlot[slot].rows)
	{
		PyErr_SetString(PyExc_RuntimeError, "There is no data to fetch.");
		return NULL;
	}

	if(idx < 0 || idx > rSlot[slot].cols)
	{
		PyErr_SetString(PyExc_RuntimeError, "The column index is out of range.");
		return NULL;
	}
		
	return PyInt_FromLong(rSlot[slot].mysql_field[idx].type);
}

static char mypysql_affectedRows_doc[]= 
"affectedRows()\n\
Return the number of rows affected by previous command.";

static PyObject* mypysql_affectedRows(PyObject *self, PyObject *args)
{
	return PyLong_FromLong((long)lastAffectedRows);
}

static char mypysql_eor_doc[]= 
"eor(slot)\n\
End Of Record, true when you reach the end of a record. If you call \
'next' when you are at the last row the EOF will become true.";

static PyObject* mypysql_eor(PyObject *self, PyObject *args)
{
	int slot;
	
	if(!PyArg_ParseTuple(args, "l:eor", &slot)) 
		return NULL;

	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}

	return PyInt_FromLong(rSlot[slot].eor);
}

static char mypysql_getLastInsertId_doc[]= 
"getLastInsertId(slot)\n\
Return the id created for the last insert operation.";

static PyObject* mypysql_getLastInsertId(PyObject *self, PyObject *args)
{
	int slot;
	
	if(!PyArg_ParseTuple(args, "l:eor", &slot)) 
		return NULL;

	if(slot < 0 || slot > MAXSLOTS-1)
	{
		PyErr_SetString(PyExc_RuntimeError, "Invalid slot number. 0 to 4 allowed.");
		return NULL;
	}

	return PyInt_FromLong(rSlot[slot].lastInsert);
}

//////////////////////////////////////////////////////////////////////////
// List of exported functions

static PyMethodDef mypysql_methods[] = {
			{"setOptions", mypysql_setOptions, METH_VARARGS, mypysql_setOptions_doc},
			{"setAccess", mypysql_setAccess, METH_VARARGS, mypysql_setAccess_doc},
			{"open", mypysql_open, METH_VARARGS, mypysql_open_doc},
			{"close", mypysql_close, METH_NOARGS, mypysql_close_doc},
			{"execute", mypysql_execute, METH_VARARGS, mypysql_execute_doc},
			{"query", mypysql_query, METH_VARARGS, mypysql_query_doc},
			{"rows", mypysql_rows, METH_VARARGS, mypysql_rows_doc},
			{"cols", mypysql_cols, METH_VARARGS, mypysql_cols_doc},
			{"next", mypysql_next, METH_VARARGS, mypysql_next_doc},
			{"field", mypysql_field, METH_VARARGS, mypysql_field_doc},
			{"getColName", mypysql_getColName, METH_VARARGS, mypysql_getColName_doc},
			{"getColType", mypysql_getColType, METH_VARARGS, mypysql_getColType_doc},
			{"affectedRows", mypysql_affectedRows, METH_NOARGS, mypysql_affectedRows_doc},
			{"eor", mypysql_eor, METH_VARARGS, mypysql_eor_doc},
			{"getLastInsertId", mypysql_getLastInsertId, METH_VARARGS, mypysql_getLastInsertId_doc},
			{NULL, NULL}
		};

//////////////////////////////////////////////////////////////////////////
// Entry code

PyMODINIT_FUNC initmypysql(void)
{
	int i;
	
	Py_InitModule3("mypysql", mypysql_methods, mypysql_doc);
	
	//carrega a DLL do mysql
	dll= LoadLibrary("libmysql.dll");
	
	mps_init= (PTRmysql_init)GetProcAddress(dll, "mysql_init");
	mps_errno= (PTRmysql_errno)GetProcAddress(dll, "mysql_errno");
	mps_real_connect= (PTRmysql_real_connect)GetProcAddress(dll, "mysql_real_connect");
	mps_close= (PTRmysql_close)GetProcAddress(dll, "mysql_close");
	mps_query= (PTRmysql_query)GetProcAddress(dll, "mysql_query");
	mps_store_result= (PTRmysql_store_result)GetProcAddress(dll, "mysql_store_result");
	mps_num_rows= (PTRmysql_num_rows)GetProcAddress(dll, "mysql_num_rows");
	mps_num_fields= (PTRmysql_num_fields)GetProcAddress(dll, "mysql_num_fields");
	mps_fetch_fields= (PTRmysql_fetch_fields)GetProcAddress(dll, "mysql_fetch_fields");
	mps_field_count= (PTRmysql_field_count)GetProcAddress(dll, "mysql_field_count");
	mps_affected_rows= (PTRmysql_affected_rows)GetProcAddress(dll, "mysql_affected_rows");
	mps_fetch_row= (PTRmysql_fetch_row)GetProcAddress(dll, "mysql_fetch_row");
	mps_free_result= (PTRmysql_free_result)GetProcAddress(dll, "mysql_free_result");
	mps_error= (PTRmysql_error)GetProcAddress(dll, "mysql_error");	
	
	//inicializa tudo
	
	mps_init(&mysql_h);
	
	dbHost[0]= dbDB[0]= dbUser[0]= dbPass[0]= dbPort= 0;
	options= 0;
	lastAffectedRows= 0;
	
	for(i= 0; i < MAXSLOTS; i++)
	{
		rSlot[i].mysql_res= NULL;
		rSlot[i].rows= rSlot[i].cols= 0;
		rSlot[i].eor= true;
	}
}